/*
==========================================================
DX490a - Summer 2010
Instructor: Stelios Manousakis
==========================================================
Class 4.1:
Buffers and interdependent processes
Contents:
• Buffers
- About
- Synchronous vs Asyncronous
completion messages
action function
• Condition
==========================================================
*/
// ================= BUFFERS =================
// Buffers are essentially arrays of floating point numbers (up to 32-bit), that you load in RAM and store in the Server, making them globally available. We tend to use buffers for audio, but you can really put in them any kind of data you 'd like to store temporarily or permanently.
// The number of buffers you can load in a Server is pre-assigned before you boot the Server from the ServerOptions. The default is 1024 buffers:
Server.internal.options.numBuffers
// The actual number of buffers you can load simultaneously, really depends on their length, and on the available memory of your machine.
// Buffers can be accessed by referring to their index number inside the server, their bufnum; think of this as accessing a specific member inside an array. When loading a buffer, a bufnum will be automatically allocated to it, unless you provide one yourself to know where it is. Nevertheless, a much easier way to access a buffer is to assign it a specific variable in the language.
b = Buffer.read(s, "sounds/a11wlk01.wav");
b.bufnum.postln; // see its bufnum
// Loading buffers takes up valuable memory space, so don't forget to .free a buffer once you're done using it! Look at the memory load increase and decrease in Activity Monitor (OSX only):
"open '/Applications/Utilities/Activity Monitor.app'".unixCmd
// let's load 20 buffers of 20 seconds:
s.waitForBoot{ ~a = Array.fill(20, {Buffer.alloc(s,44100 * 12.0, 2)})};
// You can see that the memory load increased
// And now:
20.do({arg i; ~a[i].free;})
// or
~a.collect(_.free;)
// It went back down again
// ====== Synchronous vs Asyncronous ======
// A buffer of just 1 second with a 44.1kHz sampling rate contains 44100 floating point values. Allocating the appropriate RAM and sending all that data to the server to fill that space up may take a while - and you can't really know how long, as it depends on your computer's available resources at any given moment (usually less than a second, but for very very large soundfiles it can take much longer). In order to make sure that this activity will not get in the way of other processes that are running, the Server loads buffers in a lower priority thread, running them asynchronously - that is, at its own convenience.
// Given this property of reading (and writing) buffers, the Buffer class provides you with two methods for knowing when a buffer has finished loading (or writing), so that you can go on using it:
// ------ Completion messages ------
// This is a second OSC command (a direct command) included in the buffer filling message sent to the server, and ensuring it will happen immediately upon the first command has been executed.
// ------ Action functions ------
// In this case, the Server sends back to sclang an OSC message once the buffer bussiness has finished; upon reception of the message, the provided function gets evaluated. Action functions take a little extra time, as they need an additional message coming back from the server, but are more flexible.
// Try this example, using a completion message:
s.boot;
// allocate an array of Buffers and fill them with different harmonics
(
b = Buffer.allocConsecutive(8, s, 4096, 1, { |buf, i|
buf.sine1Msg((1..((i+1)*6)).reciprocal) // completion messages to fill each buffer as soon as it's created
});
a = { VOsc.ar(SinOsc.kr(0.5, 0).range(b.first.bufnum + 0.1, b.last.bufnum - 0.1), [440, 441], 0, 0.2) }.play;
)
// free the synth:
a.free;
// iterate over the array of buffers and free it:
b.do(_.free);
// Another example, using an action function:
(
b = Buffer.read(s, "sounds/a11wlk01.wav", action: { arg buffer;
("After update:" + buffer.numFrames).postln;
x = { PlayBuf.ar(1, buffer, BufRateScale.kr(buffer)) }.play;
});
("Before update:" + b.numFrames).postln; // notice that this happens first!
)
x.free; b.free;
///// Also check these two methods inside Buffer: very handy if you want to send very large data
.loadCollection
.sendCollection
// Notice that if you want to create and modify a buffer within a SynthDef, you should use LocalBuf instead!
// ================= CONDITION =================
// [Condition] is a very useful class which you can use to create networks of interdependend processes:
// For example:
(
// create a condition
c = Condition.new(false);
// a process
~process1 = {arg input;
r = Routine {
// 1
input.postln;
1.wait;
// 2
"waited for 1 second".postln;
input = (input * 10).postln;
1.wait;
// 3
"waited for another second, now waiting for you ... ".postln;
input = (input * 10).postln;
c.wait;
// 4
"the condition has stopped waiting.".postln;
input = (input * 10).postln;
1.wait;
// 5
"waited for another second".postln;
"waiting for you ... ".postln;
c.test = false;
input = (input * 100).postln;
c.wait;
// 6
"the condition has stopped waiting.".postln;
input = (input * 10).postln;
1.wait;
// 7
input = (input * 0.0001).postln;
"the end".postln;
};
r.play;
};
// another process
~process2 = {arg input;
if ((input > 0).postln, {c.test = true;
c.signal})
};
);
~process1.value(23); // start first process
// do this when it stops:
~process2.value(-1); // second process is false; nothing happens
// try again:
~process2.value(1.0);
// once more:
~process2.value(1.0);
///////////////////////////////
// Let's try a sound example:
(note that this exampe is just for demonstration purposes, you woudn't ordinarily want to use {}.play for creating synths in such manner):
s.boot;
// allocate a buffer:
~test = Buffer.alloc(s, 1024);
(
t = TempoClock(1);
// create a condition
c = Condition.new(false);
// A function to create random wavetables:
~wrand = {|bufnum, startTime, size, segments, minVal, maxVal, interp, action|
var pointVals, segLengths, env, sampVals;
pointVals = Array.rand(segments, minVal, maxVal).cubed;
segLengths = (Array.rand(segments - 1, 1, 100).cubed).normalizeSum;
env = Env(pointVals, segLengths, interp);
sampVals = sampVals.addAll(env.asSignal(size));
SystemClock.sched(startTime, {bufnum.sendCollection(sampVals, action: action)});
};
// the function to make some sound in a Routine:
~process1 = {arg interpol; // this should be a valid interpolation method
r = Routine ({
loop ({
~wrand.value(~test, 0, ~test.numFrames, 5.rrand(500), -1.rrand(-0.333), 1.0.rrand(0.333), interpol, 2, action: {|buf|
// do this once the buffer is ready:
// free the synths if they're playing already
p.free;
q.free;
// then start two new instances
p = { Out.ar(0, PlayBuf.ar(1, ~test, BufRateScale.kr(~test) * 1.0111, loop: 1) * 0.2 )}.play;
q = { Out.ar(1, PlayBuf.ar(1, ~test, BufRateScale.kr(~test) * 0.98567, loop: 1) * 0.2 )}.play;
});
[0.125, 0.25, 0.5, 1].wchoose([0.85, 0.09, 0.04, 0.02]).wait;
c.wait;
// do it twice to avoid glitches from freeing
~wrand.value(~test, 0, ~test.numFrames, 5.rrand(500), -1.rrand(-0.333), 1.0.rrand(0.333), interpol, 2, action: {|buf|
// free the other two synths if they're playing
x.free;
y.free;
// then start two more new instances
x = { Out.ar(0, PlayBuf.ar(1, ~test, BufRateScale.kr(~test) * 1.0139, loop: 1) * 0.2 )}.play;
y = { Out.ar(1, PlayBuf.ar(1, ~test, BufRateScale.kr(~test) * 0.98432, loop: 1) * 0.2 )}.play;
});
[0.125, 0.25, 0.5, 1].wchoose([0.85, 0.1, 0.04, 0.01]).wait;
c.wait;
});
});
r.play(t);
};
)
~process1.value(\sin); // choose an interpolation type
(
// start triggering random wavetables by focusing on the SCWindow and pressing the spacebar; stop with any key
w = SCWindow.new("random wavetable trigger");
w.view.keyDownAction = { arg view, char, modifiers, unicode, keycode;
if (unicode == 32, {"start".postln; c.test = true; c.signal}, {"stop".postln; c.test = false; c.signal});
};
w.front
)
t.tempo_(0.8); // change the tempo
p.free; q.free; x.free; y.free;
~test.free
/* LAB exercise:
Using what you 've learned this week about scheduling and interfacing, make up a small instrument that incorporates timed processes controllable through interaction
*/